#!coding:utf-8
"""
Created on 2013-4-16

@author: shankangjie

internal dhcp实现：

#添加网桥
brctl addbr br0
brctl setfd br0 0
brctl stp br0 off
ip l set br0 up

#将网卡加入网桥
brctl addif br0 eth0
#删除eth0上的网关信息
route del default gw 172.16.10.1 dev eth0

#将eth0上的ip加到br0
ip a del $hostip/16 dev eth0
ip a add $hostip/16 dev br0
#加入网关信息，这里已经是加在br0上了
route add default gw 172.16.10.1

#确保 sysctl net.ipv4.ip_forward=1

#启动dhcp
#绑定网卡，在所有地址上监听,涉及到dns./kvm
#kill `cat /root/nova-br100.pid`

killall dnsmasq
dnsmasq --strict-order \
--bind-interfaces \
--domain=novalocal \
--pid-file=/root/nova-br100.pid \
--except-interface=lo \
--dhcp-range=192.168.0.0,192.168.255.255,static,120s \
--dhcp-hostsfile=/root/br0.conf \
--leasefile-ro \
--listen-address=$hostip

"""

import logging, os, calendar, netaddr
from cvirt.common import utils
from cvirt.common import exception
from cvirt.common import config
from cvirt.manager import models
from cvirt.manager.models import FixedIp

LOG = logging.getLogger("cvirt.network.bridge")

#binary_name = os.path.basename(inspect.stack()[-1][1])
# binary_name = 'thor-agent'

conf = config.load_settings()


def _execute(*cmd, **kwargs):
    """Wrapper around utils._execute for fake_network."""
    if conf.get_bool('cvirt.fake.network'):
        LOG.debug('FAKE NET: %s', ' '.join(map(str, cmd)))
        return 'fake', 0
    else:
        return utils.execute(*cmd, **kwargs)


def get_binded_ips(interface):
    '''
        获取已经绑定到指定interface的ip
    '''
    if not device_exists(interface):
        raise exception.EthNotFound()

    out, err = _execute('ip', 'addr', 'show', 'dev', interface,
                        'scope', 'global')
    ips = []
    if out != 'fake':
        for line in out.split('\n'):
            fields = line.split()
            if fields and fields[0] == 'inet':
                ips.append(fields[1])

    return ips


def get_dhcp_leases(network_ref):
    """Return a network's hosts config in dnsmasq leasefile format."""
    hosts = []

    for fixed_ref in models.FixedIp.objects.filter(network=network_ref, interface__isnull=False):
        #TODO 只需要本机的数据
        hosts.append(_host_lease(fixed_ref))
    return '\n'.join(hosts)


def get_dhcp_hosts(network_ref, host):
    """Get network's hosts config in dhcp-host format."""
    hosts = []
    for fixed_ref in models.FixedIp.objects.filter(network=network_ref, interface__isnull=False):
        #fixed:只需要本机所属的fixed ip
        if fixed_ref.interface.instance.host.ip != host:
            continue
        hosts.append(_host_dhcp(fixed_ref))
    return '\n'.join(hosts)


def release_dhcp(dev, address, mac_address):
    try:
        _execute('dhcp_release', dev, address, mac_address)
    except Exception as e:
        LOG.warn('dhcp release failed,exception is %s' % e)


def _stop_dnsmasq(dev):
    """Stops the dnsmasq instance for a given network."""
    pid = _dnsmasq_pid_for(dev)

    if pid:
        try:
            _execute('kill', '-TERM', pid)
        except Exception as exc:  # pylint: disable=W0703
            LOG.debug('Killing dnsmasq threw %s', exc)


def restart_network(network_ref,host=conf['cnode.host'],last_network_ip=None,last_cidr=None):
    _stop_dnsmasq(network_ref.bridge)
    #关闭dhcp后移除上一次开启dhcp绑定在网桥上的network ip,先删除，后重新加上
    if last_network_ip:
        LOG.debug("remove network ip %s " % last_network_ip.address)
        remove_network_ip(network_ref.bridge,last_network_ip,network_ref,last_cidr=last_cidr)
    #如果没有开启dhcp，就不需要启动dnsmasq了
    if network_ref.rundhcp:
        network_ip = FixedIp.objects.get_or_none(host=host,allocated=True,network=network_ref)
        if network_ip:
            LOG.debug("network %s's network ip is %s " % (network_ref.name, network_ip.address))
            bind_network_ip(network_ref.bridge,network_ip,network_ref)
        update_dhcp(network_ref,host)


@utils.synchronized('dnsmasq_start')
def update_dhcp(network_ref, host):
    """(Re)starts a dnsmasq server for networks belongs to host.

    If a dnsmasq instance is already running then send a HUP
    signal causing it to reload, otherwise spawn a new instance.

    """
    dev = network_ref.bridge
    conffile = _dhcp_file(dev, 'conf')

    with open(conffile, 'w') as f:
        f.write(get_dhcp_hosts(network_ref, host).encode('UTF-8'))

    os.chmod(conffile, 0644)

    pid = _dnsmasq_pid_for(dev)

    if pid:
        out, _err = _execute('cat', '/proc/%d/cmdline' % pid,
                             check_exit_code=False)
        if conffile in out:
            try:
                _execute('kill', '-HUP', pid)
                return
            except Exception as exc:
                LOG.debug('Hupping dnsmasq threw %s', exc)
        else:
            LOG.debug('Pid %d is stale, relaunching dnsmasq', pid)

    #kill ubuntu自带的dnsmasq
    try:
        os.system("sudo ps aux|grep dnsmasq|grep var/run/dnsmasq/resolv.conf|awk '{print $2}'|xargs kill")
    except:
        pass

    cmd = ['NETWORK_ID=%s' % str(network_ref.id),
           'dnsmasq',
           '--strict-order',
           '--listen-address=%s' % allocate_ip_for_network(network_ref).address,
           '--bind-interfaces',
           '--interface=%s' % dev,
           '--domain=%s' % conf['dhcp_domain'],
           '--pid-file=%s' % _dhcp_file(dev, 'pid'),
           '--except-interface=lo',
           # '--dhcp-range=%s,%s,static,12000s' % (network_ref.dhcp_start, network_ref.dhcp_end),
           #important!!!!!!! dhcp_range如果start和end都给，将会覆盖hostsfile选项
           '--dhcp-range=%s,static,%ss' % (network_ref.dhcp_start,conf['dhcp_lease_time']),
           '--dhcp-lease-max=%s' % netaddr.IPRange(network_ref.dhcp_start, network_ref.dhcp_end).size,
           '--dhcp-hostsfile=%s' % _dhcp_file(dev, 'conf'),
           '--dhcp-script=%s' % conf['dhcpbridge'],
           '--leasefile-ro',
           '--dhcp-option=252,"\\n"',   #for win7 dhcp client
           '--dhcp-no-override',  #for pxe install
           # '--log-facility=%s' % _dhcp_file(dev,'log'),
            ]

    gateway = _if_gateway(dev) if network_ref.gateway is None else network_ref.gateway
    if gateway:
        cmd += ['--dhcp-option=3,%s' % gateway]

    dns = []
    if network_ref.dns1:
        dns.append(network_ref.dns1)
    if network_ref.dns2:
        dns.append(network_ref.dns2)

    #指定dns1,默认使用本地dns(resole.conf)
    if dns:
        # cmd += ['-h', '-R', '--server=%s' % network_ref.dns1]

        #-h = do not read /etc/hosts
        #-R = do not read /etc/resolv.conf
        cmd += ['-h', '-R', '--dhcp-option=6,%s' % (','.join(dns))] #直接使用外部dns，不使用本地加速

    _execute(*cmd, run_as_root=True)


def _host_lease(fixed_ip_ref):
    """Return a host string for an address in leasefile format."""
    instance_ref = fixed_ip_ref.interface.instance
    if instance_ref.updated_at:
        timestamp = instance_ref.updated_at
    else:
        timestamp = instance_ref.created_at

    seconds_since_epoch = calendar.timegm(timestamp.utctimetuple())

    return '%d %s %s %s *' % (seconds_since_epoch + conf.get_int('dhcp_lease_time'),
                              fixed_ip_ref.interface.mac,
                              instance_ref.mac_address,
                              instance_ref.name or '*')


def _host_dhcp(fixed_ip_ref):
    """返回dhcp-host
    格式：mac,hostname,ip
    """
    instance_ref = fixed_ip_ref.interface.instance
    return '%s,%s,%s' % (fixed_ip_ref.interface.mac,
                         instance_ref.name.replace(' ', ''), #jira DESKTOP-97
                         fixed_ip_ref.address)


def device_exists(device):
    """Check if ethernet device exists."""
    (_out, err) = _execute('ip', 'link', 'show', 'dev', device,
                           check_exit_code=False)
    # if conf.get_bool('cvirt.fake.network'):
    #     return False
    return not err


def _stop_dnsmasq(dev):
    """Stops the dnsmasq instance for a given network."""
    pid = _dnsmasq_pid_for(dev)

    if pid:
        try:
            _execute('kill', '-TERM', pid)
        except Exception as exc:  # pylint: disable=W0703
            LOG.debug('Killing dnsmasq threw %s', exc)


def _dhcp_file(dev, kind):
    """Return path to a pid, leases or conf file for a bridge/device."""
    if not os.path.exists(conf['networks_path']):
        os.makedirs(conf['networks_path'])
    return os.path.abspath('%s/thor-%s.%s' % (conf['networks_path'],
                                              dev,
                                              kind))


def _dnsmasq_pid_for(dev):
    """Returns the pid for prior dnsmasq instance for a bridge/device.

    Returns None if no pid file exists.

    If machine has rebooted pid might be incorrect (caller should check).

    """
    pid_file = _dhcp_file(dev, 'pid')

    if os.path.exists(pid_file):
        with open(pid_file, 'r') as f:
            return int(f.read())


def _ip_bridge_cmd(action, params, device):
    """Build commands to add/del ips to bridges/devices."""
    cmd = ['ip', 'addr', action]
    cmd.extend(params)
    cmd.extend(['dev', device])
    return cmd


def _if_gateway(interface):
    out, _ = _execute('route', '-n')
    for line in out.split('\n'):
        fields = line.split()
        if fields and fields[0] == '0.0.0.0' and \
                        fields[-1] == interface:
            return fields[1]


def ensure_vlan(network_ref):
    if network_ref:
        vlanface = "vlan%s" % network_ref.vlanid
        #vlan网卡命名规则:vlan[id]
        if not device_exists(vlanface):
            _execute("vconfig", "set_name_type", "VLAN_PLUS_VID_NO_PAD")

            _execute('vconfig', 'add', conf['bond.name'], network_ref.vlanid)
        return vlanface


def allocate_ip_for_network(network_ref,host = conf['cnode.host']):
    if network_ref.rundhcp:
        network_ip = models.FixedIp.objects.first(allocated=True, host=host, network=network_ref)
        if not network_ip:
            network_ip = models.FixedIp.objects.filter(network=network_ref, allocated=False).for_update()[0]
            LOG.debug("allocate ip %s in host %s for network %s " % (network_ip.address,host,network_ref.name))
            if not network_ip:
                raise exception.FixedIpNoMoreForNetwork()
            network_ip.update(dict(host=host, allocated=True))
        return network_ip


@utils.synchronized('ensure_bridge', external=True)
def ensure_bridge(network_ref, interface):
    '''
    初始化网桥
    1.增加网桥
    2.绑定private interface到网桥
    3.绑定private interface上的网关信息到网桥(如果存在）
    4.允许网桥forward规则
    5.绑定network ip到interface
    '''
    #获取network host ip，没有则中断
    bridge = network_ref['bridge']
    # interface = network_ref.bridge_interface.name
    # interface = conf['bond.name']
    LOG.info('ensure bridge %s for network %s' % (bridge, network_ref.name))

    network_ip = allocate_ip_for_network(network_ref)

    if not device_exists(bridge):
        LOG.debug('Starting Bridge interface for %s', interface)
        _execute('brctl', 'addbr', bridge)
        _execute('brctl', 'setfd', bridge, 0)
        _execute('brctl', 'stp', bridge, network_ref.stp)

        _execute('ip', 'link', 'set', bridge, 'up')

    if interface:
        out, err = _execute('brctl', 'addif', bridge, interface,
                            check_exit_code=False)

        gateway = _if_gateway(interface)
        if gateway:
            _execute('route', 'del', 'default', 'gw', gateway,
                     'dev', interface, check_exit_code=False,
                     run_as_root=True)
        out, err = _execute('ip', 'addr', 'show', 'dev', interface,
                            'scope', 'global')
        for line in out.split('\n'):
            fields = line.split()
            if fields and fields[0] == 'inet':
                params = fields[1:-1]
                _execute(*_ip_bridge_cmd('del', params, fields[-1]),
                         run_as_root=True)
                _execute(*_ip_bridge_cmd('add', params, bridge),
                         run_as_root=True)
        if gateway:
            #加入网关信息
            _execute('route', 'add', 'default', 'gw', gateway,
                     run_as_root=True)

        if (err and err != "device %s is already a member of a bridge;"
                           "can't enslave it to bridge %s.\n" % (interface, bridge)):
            raise exception.Error('Failed to add interface: %s' % err)

    bind_network_ip(bridge, network_ip, network_ref)

def bind_network_ip(bridge, network_ip, network_ref):
    # 绑定network ip到interface
    if network_ref and network_ref.rundhcp:
        ips = get_binded_ips(bridge)
        n_ip = "%s/%s" % (network_ip.address,
                          network_ref['cidr'].split('/')[-1])
        if not n_ip in ips:
            _execute('ip', 'a', 'add', n_ip, 'dev', bridge,
                     run_as_root=True)


def remove_network_ip(bridge, network_ip, network_ref,last_cidr=None):
    ips = get_binded_ips(bridge)
    #last_cidr是上次网络绑定的network ip
    cidr = network_ref['cidr'] or last_cidr
    if cidr:
        n_ip = "%s/%s" % (network_ip.address,
                          cidr.split('/')[-1])
        if n_ip in ips:
            _execute('ip', 'a', 'del', n_ip, 'dev', bridge,
                     run_as_root=True)


@utils.synchronized('remove_bridge', external=True)
def remove_bridge(network_ref, interface):
    '''
    remove bridge,操作基本和ensure_bridge相反
    1.remove network ip(stop dhcp)
    2.remove interface from bridge
    3.remove gw from bridge
    4.add gw to interface
    5.delete bridge
    '''

    bridge = network_ref['bridge']
    LOG.info('remove bridge %s for network %s' % (bridge, network_ref.name))
    host = conf['cnode.host']
    if network_ref.rundhcp:
        network_ip = FixedIp.objects.first(host=host,allocated=True,network=network_ref)


    _stop_dnsmasq(bridge)

    if network_ref and network_ref.rundhcp:
        #vlan的网卡设备已经被移除，不需要处理network ip
        if network_ref.networktype == 2:  #bridge
            remove_network_ip(bridge, network_ip, network_ref)

        network_ip.update(dict(host=None,allocated=False))

    if interface:
        out, err = _execute('brctl', 'delif', bridge, interface,
                            check_exit_code=False)

        gateway = _if_gateway(bridge)
        if gateway:
            _execute('route', 'del', 'default', 'gw', gateway,
                     'dev', bridge, check_exit_code=False,
                     run_as_root=True)
        out, err = _execute('ip', 'addr', 'show', 'dev', bridge,
                            'scope', 'global')
        for line in out.split('\n'):
            fields = line.split()
            if fields and fields[0] == 'inet':
                params = fields[1:-1]
                _execute(*_ip_bridge_cmd('del', params, fields[-1]),
                         run_as_root=True)
                _execute(*_ip_bridge_cmd('add', params, interface),
                         run_as_root=True)
        if gateway:
            #加入网关信息
            _execute('route', 'add', 'default', 'gw', gateway,
                     run_as_root=True)

    if device_exists(bridge):
        LOG.debug('remove Bridge interface for %s', interface)
        _execute('ip', 'link', 'set', bridge, 'down')
        _execute('brctl', 'delbr', bridge)

    _execute('ip', 'link', 'set', interface, 'up',check_exit_code=False)


def remove_vlan(network_ref):
    if network_ref:
        vlanface = "vlan%s" % network_ref.vlanid
        #vlan网卡命名规则:vlan[id]
        if device_exists(vlanface):
            _execute('vconfig', 'rem', vlanface)
        return vlanface